/*
 * Copyright (c) 2018. Manuel D. Rossetti, rossetti@uark.edu
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package jsl.modeling.elements;

import jsl.simulation.EventActionIfc;
import jsl.simulation.JSLEvent;
import jsl.simulation.ModelElement;
import jsl.simulation.SchedulingElement;
import jsl.modeling.elements.variable.RandomVariable;
import jsl.utilities.GetValueIfc;
import jsl.utilities.random.RandomIfc;
import jsl.utilities.random.rvariable.ConstantRV;

/**
 * This class allows for the periodic generation of events similar to that
 * achieved by "Create" modules in simulation languages. This class works in
 * conjunction with the EventGeneratorActionIfc which is used to listen and
 * react to the events that are generated by this class.
 * <p>
 * Classes can supply an instance of an EventGeneratorActionIfc to provide the
 * actions that take place when the event occurs. Alternatively, if no
 * EventGeneratorActionIfc is supplied, by default the generator(JSLEvent
 * event) method of this class will be called when the event occurs. Thus,
 * sub-classes can simply override this method to provide behavior for when the
 * event occurs. If no EventGeneratorActionIfc is supplied and the generate()
 * method is not overridden, then the events will still occur; however, no
 * meaningful actions will take place.
 * <p>
 * Of particular note is the use of initial parameters:
 * <p>
 * initial time of first event (default = Constant.ZERO)
 * <p>
 * initial time between events (default = Constant.POSITIVE_INFINITY)
 * <p>
 * initial maximum number of events (default = Long.MAX_VALUE)
 * <p>
 * initial ending time (default = Double.POSITIVE_INFINITY)
 * <p>
 * <p>
 * These parameters control the initial state of the generator at the start
 * of each replication. The generator is re-initialized to these values at
 * the start of each replication. There are also parameters for each of these
 * that can be changed during a replication. The effect of that change
 * is only within the current replication.
 */
public class EventGenerator extends SchedulingElement implements EventGeneratorIfc {

    /**
     * Determines the priority of the event generator's events The default is
     * DEFAULT_PRIORITY - 1 A lower number implies higher priority.
     */
    public final static int EVENT_PRIORITY = JSLEvent.DEFAULT_PRIORITY - 1;

    /**
     * Determines the priority of the event generator's events The default is
     * DEFAULT_PRIORITY - 1 A lower number implies higher priority.
     */
    private int myEventPriority = EVENT_PRIORITY;

    /**
     * Holds the random source for the time until first event. Used to
     * initialized the generator at the beginning of each replication
     */
    private RandomIfc myInitialTimeUntilFirstEvent;

    /**
     * A RandomVariable that uses the time until first random source
     */
    private RandomVariable myTimeUntilFirstEventRV;

    /**
     * Holds the random source for the time between events. Used to initialized
     * the generator at the beginning of each replication
     */
    private RandomIfc myInitialTimeBtwEvents;

    /**
     * A random variable for the time between events
     */
    private RandomVariable myTimeBtwEventsRV;

    /**
     * Used to set the ending time when the generator is initialized
     */
    private double myInitialEndingTime;

    /**
     * The time to stop generating for the current replication
     */
    private double myEndingTime;

    /**
     * Used to initialize the maximum number of events at the beginning of each
     * replication
     */
    private long myInitialMaxNumEvents;

    /**
     * The number of events to generate for the current replication
     */
    private long myMaxNumEvents;

    /**
     * The number of events currently generated during the replication
     */
    private long myEventCount;

    /**
     * Whether or not the generator is done generating
     */
    private boolean myDoneFlag;

    /**
     * Whether or not the generator has been suspended
     */
    private boolean mySuspendedFlag;

    /**
     * The next event to be executed for the generator
     */
    private JSLEvent<String> myNextEvent;

    /**
     * Handles the actions for the event
     */
    private EventGeneratorActionIfc myGenerateListener;

    /**
     * This flag controls whether or not the generator starts automatically when
     * initialized at the beginning of a replication By default this option is
     * true. If it is changed then it remains at the set value until changed
     * again.
     */
    private boolean myStartOnInitFlag = true;

    private final EventHandler myEventHandler;

    /**
     * indicates whether or not the generator has been started (turned on)
     * <p>
     */
    private boolean myStartedFlag = false;

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE) Default time
     * between next is infinity. The default time of the first event is 0.0
     *
     * @param parent the parent model element
     */
    public EventGenerator(ModelElement parent) {
        this(parent, null, ConstantRV.ZERO, ConstantRV.POSITIVE_INFINITY,
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE) Default time
     * between next is infinity. The default time of the first event is 0.0
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     */
    public EventGenerator(ModelElement parent, EventGeneratorActionIfc listener) {
        this(parent, listener, ConstantRV.ZERO, ConstantRV.POSITIVE_INFINITY,
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE) Default time
     * between next is infinity. The default time of the first event is 0.0
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param name The name of the generator.
     */
    public EventGenerator(ModelElement parent, EventGeneratorActionIfc listener, String name) {
        this(parent, listener, ConstantRV.ZERO, ConstantRV.POSITIVE_INFINITY,
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, name);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE) Default time
     * between next is infinity.
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A DistributionIfc object that supplies the time
     * until the first event
     *
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst) {
        this(parent, listener, timeUntilFirst, ConstantRV.POSITIVE_INFINITY,
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE)
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     *
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext) {
        this(parent, listener, timeUntilFirst, timeUntilNext, 
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE) Default time
     * between next is infinity.
     *
     * @param parent the parent model element
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     */
    public EventGenerator(ModelElement parent, RandomIfc timeUntilFirst) {
        this(parent, null, timeUntilFirst, ConstantRV.POSITIVE_INFINITY,
                Long.MAX_VALUE, Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE)
     *
     * @param parent the parent model element
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     *
     */
    public EventGenerator(ModelElement parent, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext) {
        this(parent, null, timeUntilFirst, timeUntilNext, Long.MAX_VALUE, 
                Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE)
     *
     * @param parent the parent model element
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     * @param name the name of the generator
     *
     */
    public EventGenerator(ModelElement parent, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext, String name) {
        this(parent, null, timeUntilFirst, timeUntilNext, Long.MAX_VALUE,
                Double.POSITIVE_INFINITY, name);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events. The default maximum
     * number of events to generate is infinite (Long.MAX_VALUE)
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     * @param name the name of the generator
     *
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext, String name) {
        this(parent, listener, timeUntilFirst, timeUntilNext, Long.MAX_VALUE,
                Double.POSITIVE_INFINITY, name);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events.
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     * @param maxNum A long that supplies the maximum number of events to
     * generate. Each time an event is to be scheduled the maximum number of
     * events is checked. If the maximum has been reached, then the generator is
     * turned off. The default is Long.MAX_VALUE. This parameter cannot be
     * Long.MAX_VALUE when the time until next always returns a value of 0.0
     *
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext, long maxNum) {
        this(parent, listener, timeUntilFirst, timeUntilNext, maxNum, 
                Double.POSITIVE_INFINITY, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events.
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     * @param maxNum A long that supplies the maximum number of events to
     * generate. Each time an event is to be scheduled the maximum number of
     * events is checked. If the maximum has been reached, then the generator is
     * turned off. The default is Long.MAX_VALUE. This parameter cannot be
     * Long.MAX_VALUE when the time until next always returns a value of 0.0
     * @param timeUntilLast A double that supplies a time to stop generating
     * events. When the generator is created, this variable is used to set the
     * ending time of the generator. Each time an event is to be scheduled the
     * ending time is checked. If the time of the next event is past this time,
     * then the generator is turned off and the event won't be scheduled. The
     * default is Double.POSITIVE_INFINITY.
     *
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext, long maxNum, double timeUntilLast) {
        this(parent, listener, timeUntilFirst, timeUntilNext, maxNum, timeUntilLast, null);
    }

    /**
     * Creates an EventGenerator that uses the supplied
     * EventGeneratorActionIfc to react to the events.
     *
     * @param parent the parent model element
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     * @param timeUntilFirst A RandomIfc object that supplies the time until the
     * first event.
     * @param timeUntilNext A RandomIfc object that supplies the time between
     * events. Must not be a RandomIfc that always returns 0.0, if the maximum
     * number of generations is infinite (Long.MAX_VALUE)
     * @param maxNum A long that supplies the maximum number of events to
     * generate. Each time an event is to be scheduled the maximum number of
     * events is checked. If the maximum has been reached, then the generator is
     * turned off. The default is Long.MAX_VALUE. This parameter cannot be
     * Long.MAX_VALUE when the time until next always returns a value of 0.0
     * @param timeUntilLast A double that supplies a time to stop generating
     * events. When the generator is created, this variable is used to set the
     * ending time of the generator. Each time an event is to be scheduled the
     * ending time is checked. If the time of the next event is past this time,
     * then the generator is turned off and the event won't be scheduled. The
     * default is Double.POSITIVE_INFINITY.
     * @param name The name of the generator.
     */
    public EventGenerator(ModelElement parent,
            EventGeneratorActionIfc listener, RandomIfc timeUntilFirst,
                          RandomIfc timeUntilNext, long maxNum, double timeUntilLast, String name) {
        super(parent, name);
        myEventHandler = new EventHandler();
        setAfterReplicationOption(false);
        setEventGeneratorListener(listener);

        myDoneFlag = false;
        mySuspendedFlag = false;
        myEventCount = 0;
        myNextEvent = null;

        if (timeUntilFirst == null) {
            timeUntilFirst = ConstantRV.ZERO;
        }

        setInitialTimeUntilFirstEvent(timeUntilFirst);
        myTimeUntilFirstEventRV = new RandomVariable(this, timeUntilFirst, getName() + ":TimeUntilFirstRV");

        if (timeUntilNext == null) {
            timeUntilNext = ConstantRV.POSITIVE_INFINITY;
        }

        setInitialTimeBetweenEventsAndMaxNumEvents(timeUntilNext, maxNum);

        setTimeBetweenEvents(myInitialTimeBtwEvents, myInitialMaxNumEvents);

        setInitialEndingTime(timeUntilLast);

        // set ending time based on the value to be used for each replication
        // setEndingTime(myInitialEndingTime);
    }

    public static ActionStepIfc builder(ModelElement parent) {
        return new EventGeneratorBuilder(parent);
    }

    public static interface ActionStepIfc {

        TimeBetweenEventsStepIfc action(EventGeneratorActionIfc action);
    }

    public static interface TimeBetweenEventsStepIfc {

        BuildStepIfc timeBetweenEvents(RandomIfc timeBtwEvents);
    }

    public static interface BuildStepIfc {

        BuildStepIfc timeUntilFirst(RandomIfc timeUntilFirst);

        BuildStepIfc maxNumberOfEvents(long maxNum);

        BuildStepIfc name(String name);

        BuildStepIfc timeUntilLastEvent(double timeUntilLastEvent);

        EventGenerator build();
    }

    protected static class EventGeneratorBuilder implements ActionStepIfc,
            TimeBetweenEventsStepIfc, BuildStepIfc {

        private final ModelElement parent;
        private EventGeneratorActionIfc action;
        private RandomIfc timeUntilFirst = ConstantRV.ZERO;
        private RandomIfc timeBtwEvents;
        private long maxNum = Long.MAX_VALUE;
        private String name;
        private double timeUntilLastEvent = Double.POSITIVE_INFINITY;

        public EventGeneratorBuilder(ModelElement parent) {
            this.parent = parent;
        }

        @Override
        public TimeBetweenEventsStepIfc action(EventGeneratorActionIfc action) {
            if (action == null) {
                throw new IllegalArgumentException("The action must not be null.");
            }
            this.action = action;
            return this;
        }

        @Override
        public BuildStepIfc timeUntilFirst(RandomIfc timeUntilFirst) {
            if (timeUntilFirst == null) {
                throw new IllegalArgumentException("The time until the first event must not be null.");
            }
            this.timeUntilFirst = timeUntilFirst;
            return this;
        }

        @Override
        public BuildStepIfc timeBetweenEvents(RandomIfc timeBtwEvents) {
            if (timeBtwEvents == null) {
                throw new IllegalArgumentException("The time between events must not be null.");
            }
            this.timeBtwEvents = timeBtwEvents;
            return this;
        }

        @Override
        public BuildStepIfc maxNumberOfEvents(long maxNum) {
            if (maxNum <= 0) {
                throw new IllegalArgumentException("The maximum number of events must be > 0.");
            }
            this.maxNum = maxNum;
            return this;
        }

        @Override
        public BuildStepIfc name(String name) {
            this.name = name;
            return this;
        }

        @Override
        public BuildStepIfc timeUntilLastEvent(double timeUntilLastEvent) {
            if (timeUntilLastEvent <= 0) {
                throw new IllegalArgumentException("The time until the last event must be > 0.");
            }
            this.timeUntilLastEvent = timeUntilLastEvent;
            return this;
        }

        @Override
        public EventGenerator build() {
            return new EventGenerator(parent, action, timeUntilFirst,
                    timeBtwEvents, maxNum, timeUntilLastEvent, name);
        }

    }

    /**
     * The priority of the events for the generator
     *
     * @return the event priority
     */
    public final int getEventPriority() {
        return myEventPriority;
    }

    /**
     * Lower means earlier (higher priority). Changing the priority effects only
     * future scheduled events The changed priority is retained for all future
     * replications
     *
     * @param priority the priority to set
     */
    public final void setEventPriority(int priority) {
        myEventPriority = priority;
    }

    /**
     * Sets the listener for the events to the supplied
     * EventGeneratorActionIfc
     *
     * @param listener This listener supplies the "event" logic for reacting to
     * the generated event.
     */
    public final void setEventGeneratorListener(EventGeneratorActionIfc listener) {
        myGenerateListener = listener;
    }

    /**
     * Gets the listener for the events to the supplied
     * EventGeneratorActionIfc. This can be null. If null, the
     * generate(JSLEvent event) method is called.
     *
     * @return the event generator's action
     */
    public final EventGeneratorActionIfc getEventGeneratorListener() {
        return (myGenerateListener);
    }

    @Override
    public final boolean getStartOnInitializeFlag() {
        return myStartOnInitFlag;
    }

    @Override
    public final void setStartOnInitializeFlag(boolean flag) {
        myStartOnInitFlag = flag;
    }

    @Override
    public final void turnOnGenerator(double t) {
//		System.out.println("************************************  in turnOnGenerator() " + getName());
        if (mySuspendedFlag == true) {
            return;
        }
        if (myDoneFlag == true) {
            return;
        }
        if (myMaxNumEvents == 0) {
            return;
        }
        if (myEventCount >= myMaxNumEvents) {
            return;
        }
        if (myNextEvent != null) {
            return;
        }
//		System.out.println("************************************ scheduling the generator's first event for time " + (t + getTime()));
        // not suspended, not done, has events, no event pending
        myStartedFlag = true;
        scheduleFirstEvent(t);
    }

    @Override
    public void turnOnGenerator(RandomIfc r) {
        turnOnGenerator(r.getValue());
    }

    @Override
    public final void turnOnGenerator() {
        turnOnGenerator(0.0);
    }

    @Override
    public final void turnOffGenerator() {
        myDoneFlag = true;
        myStartedFlag = false;
        if (myNextEvent != null) {
            if (myNextEvent.isScheduled()) {
                cancelEvent(myNextEvent);
            }
        }
    }

    @Override
    public final boolean isEventPending() {
        if (myNextEvent == null) {
            return false;
        } else {
            // must be scheduled and not canceled to be pending
            return myNextEvent.isScheduled() && !myNextEvent.getCanceledFlag();
        }
    }

    @Override
    public final void suspend() {
        mySuspendedFlag = true;
        if (myNextEvent != null) {
            if (myNextEvent.isScheduled()) {
                cancelEvent(myNextEvent);
            }
        }
    }

    @Override
    public final boolean isSuspended() {
        return (mySuspendedFlag);
    }

    @Override
    public final void resume() {
        if (isSuspended()) {
            mySuspendedFlag = false;
            // get the time until next event
            double t = myTimeBtwEventsRV.getValue();
            // check if it is past end time
            if (t + getTime() > getEndingTime()) {
                turnOffGenerator();
            }

            if (myDoneFlag == false) {
                // I'm not done generating, schedule the event
                myNextEvent = scheduleEvent(myEventHandler, t, myEventPriority);
            }
        }
    }

    @Override
    public final boolean isGeneratorDone() {
        return (myDoneFlag);
    }

    @Override
    public final long getMaximumNumberOfEvents() {
        return (myMaxNumEvents);
    }

    @Override
    public final RandomIfc getTimeBetweenEvents() {
        return myTimeBtwEventsRV.getRandomSource();
    }

    @Override
    public final void setTimeBetweenEvents(RandomIfc timeUntilNext) {
        setTimeBetweenEvents(timeUntilNext, myMaxNumEvents);
    }

    @Override
    public final void setMaximumNumberOfEvents(long maxNum) {
        setTimeBetweenEvents(myTimeBtwEventsRV.getRandomSource(), maxNum);
    }

    @Override
    public final void setTimeBetweenEvents(RandomIfc timeBtwEvents, long maxNumEvents) {
        if (maxNumEvents < 0) {
            throw new IllegalArgumentException("The maximum number of actions was < 0!");
        }

        if (timeBtwEvents == null) {
            throw new IllegalArgumentException("The time until next distribution was null!");
        }

        if (maxNumEvents == Long.MAX_VALUE) {
            if (timeBtwEvents instanceof ConstantRV) {
                if (timeBtwEvents.getValue() == 0.0) {
                    throw new IllegalArgumentException("Maximum number of actions is infinite and time between actions is 0.0");
                }
            }
        }

        // time btw events is okay and max num events is okay
        myMaxNumEvents = maxNumEvents;
        if (myTimeBtwEventsRV == null) {
            myTimeBtwEventsRV = new RandomVariable(this, timeBtwEvents, getName() + " : Time Btw Events RV");
        } else {
            myTimeBtwEventsRV.setRandomSource(timeBtwEvents);
        }

        // if number of events is >= desired number of events, turn off the generator
        if (myEventCount >= maxNumEvents) {
            turnOffGenerator();
        }
    }

    @Override
    public final void setInitialTimeBetweenEvents(RandomIfc timeBtwEvents) {
        setInitialTimeBetweenEventsAndMaxNumEvents(timeBtwEvents, myInitialMaxNumEvents);
    }

    @Override
    public final void setInitialMaximumNumberOfEvents(long maxNumEvents) {
        setInitialTimeBetweenEventsAndMaxNumEvents(myInitialTimeBtwEvents, maxNumEvents);
    }

    @Override
    public final void setInitialTimeBetweenEventsAndMaxNumEvents(RandomIfc timeBtwEvents, long maxNumEvents) {
        if (timeBtwEvents == null) {
            throw new IllegalArgumentException("The time between events was null!");
        }

        if (maxNumEvents < 0) {
            throw new IllegalArgumentException("The maximum number of events to generate was < 0!");
        }

//		System.out.println("^^^^^^^^^^^^^^^^  In setTimeBetweenEventsForReplication() for " + getName());
//		System.out.println("timeBtwEvents " + timeBtwEvents + " maxNumEvents = " + maxNumEvents);
        if (maxNumEvents == Long.MAX_VALUE) {
            if (timeBtwEvents instanceof ConstantRV) {
                if (timeBtwEvents.getValue() == 0.0) {
                    throw new IllegalArgumentException("Maximum number of actions is infinite and time between actions is 0.0");
                }
            }
        }

        myInitialMaxNumEvents = maxNumEvents;
        myInitialTimeBtwEvents = timeBtwEvents;
    }

    @Override
    public final RandomIfc getInitialTimeBetweenEvents() {
        return (myInitialTimeBtwEvents);
    }

    @Override
    public final long getInitialMaximumNumberOfEvents() {
        return (myInitialMaxNumEvents);
    }

    @Override
    public final void setInitialTimeUntilFirstEvent(RandomIfc timeUntilFirst) {
        if (timeUntilFirst == null) {
            throw new IllegalArgumentException("The time until first RandomIfc was null!");
        }

        myInitialTimeUntilFirstEvent = timeUntilFirst;
    }

    @Override
    public final RandomIfc getInitialTimeUntilFirstEvent() {
        return (myInitialTimeUntilFirstEvent);
    }

    @Override
    public final void setEndingTime(double endingTime) {
        if (endingTime < 0) {
            throw new IllegalArgumentException("The ending time was < 0.0!");
        }

        if (endingTime < getTime()) {
            turnOffGenerator();
        } else // now set the time to turn off
        {
            myEndingTime = endingTime;
        }
    }

    @Override
    public final double getEndingTime() {
        return (myEndingTime);
    }

    @Override
    public final void setInitialEndingTime(double endingTime) {
        if (endingTime < 0) {
            throw new IllegalArgumentException("The time until last was < 0.0!");
        }
        myInitialEndingTime = endingTime;
    }

    @Override
    public final double getInitialEndingTime() {
        return (myInitialEndingTime);
    }

    @Override
    public final long getNumberOfEventsGenerated() {
        return (myEventCount);
    }

    @Override
    protected void initialize() {
//		System.out.println("************************** In initialize() for: " + getName());
        myDoneFlag = false;
        myStartedFlag = false;
        mySuspendedFlag = false;
        myEventCount = 0;
        myNextEvent = null;
        // set ending time based on the value to be used for each replication
        setEndingTime(myInitialEndingTime);
        // set the time until first event based on the value to be used for each replication
        myTimeUntilFirstEventRV.setRandomSource(myInitialTimeUntilFirstEvent);

        //set the time between events, maximum number of events based on the values to be used for each replication
        setTimeBetweenEvents(myInitialTimeBtwEvents, myInitialMaxNumEvents);
        if (myStartOnInitFlag) {
            if (myMaxNumEvents > 0) {
                scheduleFirstEvent(myTimeUntilFirstEventRV);
            }
        }
    }

    @Override
    public final boolean isGeneratorStarted() {
        return myStartedFlag;
    }

    /**
     * This method should be overridden by sub-classes that do not supply an
     * EventGeneratorActionIfc to model the action that occur when the event
     * happens.
     *
     * @param event the event associated with the generations
     */
    protected void generate(JSLEvent event) {
    }

    /**
     * Schedules the first event at current time + r.getValue()
     *
     * @param r the time to the first event
     */
    protected final void scheduleFirstEvent(GetValueIfc r) {
        scheduleFirstEvent(r.getValue());
    }

    /**
     * Schedules the first event at current time + t
     *
     * @param t the time to the first event
     */
    protected final void scheduleFirstEvent(double t) {
//		System.out.println("Scheduling the first event");
        // check if it is past end time
//		System.out.println(getName() + " in scheduleFirstEvent(double t) myEndingTime = " + myEndingTime);
        if (t + getTime() > getEndingTime()) {
            turnOffGenerator();
        }

        if (myDoneFlag == false) {
            // I'm not done generating, schedule the first event
            myNextEvent = scheduleEvent(myEventHandler, t, myEventPriority);
        }
    }

    /**
     * Increments the number of actions and checks if the number of actions is
     * greater than the maximum number of actions. If so, the generator is told
     * to shut down.
     */
    protected final void incrementNumberOfEvents() {
        myEventCount++;
//        System.out.println("in EventGenerator: incrementNumberOfEvents");
//        System.out.println("myEventCount = " + myEventCount);
//        System.out.println("myMaxNumEvents = " + myMaxNumEvents);
        if (myEventCount > myMaxNumEvents) {
//            myDoneFlag = true;
            turnOffGenerator();
        }
    }

    @Override
    protected void removedFromModel() {
        super.removedFromModel();
        myInitialTimeUntilFirstEvent = null;
        myTimeUntilFirstEventRV = null;
        myInitialTimeBtwEvents = null;
        myTimeBtwEventsRV = null;
        myNextEvent = null;
        myGenerateListener = null;
    }

    private class EventHandler implements EventActionIfc<String> {

        @Override
        public void action(JSLEvent<String> event) {
            incrementNumberOfEvents();

            if (myDoneFlag == false) {
                if (myGenerateListener != null) {
                    myGenerateListener.generate(EventGenerator.this, event);
                } else {
                    generate(event);
                }

                // get the time until next event
                double t = myTimeBtwEventsRV.getValue();
                // check if it is past end time
                if (t + getTime() > getEndingTime()) {
                    turnOffGenerator();
                }

                if (!isSuspended()) {
                    if (myDoneFlag == false) // I'm not done generating, schedule the next event
                    {
                        rescheduleEvent(event, t);
                    }
                }
            }
        }
    }
}
